package com.lemon.gen.service.impl;

import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.baomidou.mybatisplus.generator.FastAutoGenerator;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.builder.CustomFile;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.engine.AbstractTemplateEngine;
import com.baomidou.mybatisplus.generator.engine.VelocityTemplateEngine;
import com.baomidou.mybatisplus.generator.fill.Column;
import com.baomidou.mybatisplus.generator.fill.Property;
import com.lemon.mybatisplus.base.BaseController;
import com.lemon.mybatisplus.base.BaseServiceImpl;
import com.lemon.core.utils.ConverterUtil;
import com.lemon.core.web.Result;
import com.lemon.gen.config.GenConfig;
import com.lemon.gen.dao.TableDAO;
import com.lemon.gen.dto.TableDTO;
import com.lemon.gen.enums.DbTypeEnum;
import com.lemon.gen.enums.WebEnum;
import com.lemon.gen.po.TablePO;
import com.lemon.gen.service.ITableService;
import com.lemon.gen.vo.DbVO;
import lombok.extern.log4j.Log4j2;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import java.io.File;
import java.sql.*;
import java.time.LocalDateTime;
import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.regex.Matcher;

/**
 * 业务实现类
 *
 * @author 李猛
 * @since 2024-02-01
 */
@Log4j2
@Service
public class TableServiceImpl extends ServiceImpl<TableDAO, TablePO> implements ITableService {
    @Resource
    private GenConfig config;
    @Resource
    private Environment environment;

    /**
     * 获取所有数据库表
     *
     * @param dto
     * @return
     */
    @Override
    public Result<Map<String, Boolean>> getAllTable(TableDTO dto) {
        //1.如果是自定义数据源
        if (DbTypeEnum.CUSTOM.getType().equals(dto.getDbType()) && (!StringUtils.hasText(dto.getUrl()) || !StringUtils.hasText(dto.getPort()) || !StringUtils.hasText(dto.getDb()) ||
                !StringUtils.hasText(dto.getUsername()) || !StringUtils.hasText(dto.getPassword()))) {
            return Result.fail("请选择对应的数据源！");
        } else {
            String url = String.format("jdbc:mysql://%s:%s/%s?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai", dto.getUrl(), dto.getPort(), dto.getDb());
            dto.setUrl(url);
        }

        //2.默认数据源是读取配置文件中的数据源
        if (DbTypeEnum.DEFAULT.getType().equals(dto.getDbType())) {
            dto.setUrl(environment.getProperty("spring.datasource.url"));
            dto.setUsername(environment.getProperty("spring.datasource.username"));
            dto.setPassword(environment.getProperty("spring.datasource.password"));
        }

        //3.获取驱动
        String driver = environment.getProperty("spring.datasource.driver-class-name");
        try {
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            return Result.fail("MySQL驱动出问题了，请稍后再试！");
        }

        //4.创建连接
        Connection connection = null;
        try {
            connection = DriverManager.getConnection(dto.getUrl(), dto.getUsername(), dto.getPassword());
        } catch (SQLException e) {
            e.printStackTrace();
            return Result.fail("连接数据库失败，请检查参数是否正确！");
        }

        //5.查询所有生成的表名
        List<String> existTables = this.baseMapper.getAllTableNameByUrl(dto.getUrl());
        Map<String, Boolean> result = new HashMap<>();

        //6.获取语句
        Statement statement = null;
        ResultSet resultSet = null;
        try {
            statement = connection.createStatement();
            resultSet = statement.executeQuery("show tables");
            while (resultSet.next()) {
                String name = resultSet.getString(1);
                boolean match = existTables.stream().anyMatch(item -> item.equals(name));
                result.put(name, match);
            }
        } catch (SQLException e) {
            return Result.fail("查询失败，请稍后再试！");
        } finally {
            if (resultSet != null) {
                try {
                    resultSet.close();//后开的先关
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (statement != null) {
                try {
                    statement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
        return Result.ok(result);
    }

    /**
     * 获取默认数据库配置
     *
     * @return
     */
    @Override
    public Result<DbVO> getDbConfig() {
        return null;
    }

    /**
     * 代码生成
     *
     * @param dto
     * @return
     */
    @Override
    @Transactional
    public Result<String> generateCode(TableDTO dto) {
        //1.判空
        if (CollectionUtils.isEmpty(dto.getTableName())) {
            return Result.fail("请选择要生成的表！");
        }
        if (!StringUtils.hasText(dto.getServiceName())) {
            return Result.fail("请选择对应的微服务！");
        }

        //2.如果是自定义数据源
        if (DbTypeEnum.CUSTOM.getType().equals(dto.getDbType()) && (!StringUtils.hasText(dto.getUrl()) || !StringUtils.hasText(dto.getPort()) || !StringUtils.hasText(dto.getDb()) ||
                !StringUtils.hasText(dto.getUsername()) || !StringUtils.hasText(dto.getPassword()))) {
            return Result.fail("请选择对应的数据源！");
        } else {
            String url = String.format("jdbc:mysql://%s:%s/%s?useUnicode=true&characterEncoding=UTF-8&useSSL=false&serverTimezone=Asia/Shanghai", dto.getUrl(), dto.getPort(), dto.getDb());
            dto.setUrl(url);
        }

        //3.默认数据源是读取配置文件中的数据源
        if (DbTypeEnum.DEFAULT.getType().equals(dto.getDbType())) {
            dto.setUrl(environment.getProperty("spring.datasource.url"));
            dto.setUsername(environment.getProperty("spring.datasource.username"));
            dto.setPassword(environment.getProperty("spring.datasource.password"));
        }

        //4.设置默认值
        dto.setAuthor(StringUtils.hasText(dto.getAuthor()) ? dto.getAuthor() : config.getAuthor());
        dto.setTablePrefix(StringUtils.hasText(dto.getTablePrefix()) ? dto.getTablePrefix() : config.getTablePrefix());
        dto.setPackageName(StringUtils.hasText(dto.getPackageName()) ? dto.getPackageName() : config.getPackageName());
        dto.setWebPath(StringUtils.hasText(dto.getWebPath()) ? dto.getWebPath() : config.getWebPath());
        dto.setWebType(dto.getWebType() == null ? config.getWebType() : dto.getWebType());
        dto.setFileOverride(dto.getFileOverride() == null ? config.getFileOverride() : dto.getFileOverride());

        //5.数据库添加数据
        TablePO genTablePO = ConverterUtil.toBean(dto, TablePO.class);
        //genTablePO.setCreateBy(JwtUtil.getCurrentUser().getId());
        genTablePO.setCreateTime(LocalDateTime.now());
        this.baseMapper.insert(genTablePO);

        //6.执行生成
        try {
            generate(dto);
        } catch (RuntimeException e) {
            throw new RuntimeException("生成失败，请检查配置是否正确！");
        }
        return Result.ok("生成完成！");
    }

    /**
     * 分页查询数据
     *
     * @param tableDTO
     * @return
     */
    @Override
    public Result<Page<TablePO>> getPage(TableDTO tableDTO) {
        return null;
    }

    /**
     * 根据ID删除
     *
     * @param id
     * @return
     */
    @Override
    public Result<Void> delById(Number id) {
        return null;
    }

    private static void generate(TableDTO dto) throws RuntimeException {
        //生成配置请查看官方文档，地址：https://baomidou.com/pages/981406/#%E6%95%B0%E6%8D%AE%E5%BA%93%E9%85%8D%E7%BD%AE-datasourceconfig
        /**
         * 全局配置
         */
        String dir = System.getProperty("user.dir");
        String outputDir = String.format("%s/%s/src/main/java", dir, dto.getServiceName());
        Consumer<GlobalConfig.Builder> globalConfig = builder -> builder
                .outputDir(outputDir)//指定输出目录
                .author(dto.getAuthor())//作者名
                .disableOpenDir()//禁止打开输出目录,默认值:true
                //.enableSwagger()
                .build();

        /**
         * 策略配置
         */
        BiConsumer<Function<String, String>, StrategyConfig.Builder> strategyConfig = (scanner, builder) -> builder
                .addTablePrefix(dto.getTablePrefix())//设置表前缀移除
                .addInclude(dto.getTableName())//要生成的数据表
                .controllerBuilder()
                .superClass(BaseController.class)
                .enableRestStyle()
                .mapperBuilder()
                .formatXmlFileName("%sDAO")
                .formatMapperFileName("%sDAO")
                .serviceBuilder()
                .superServiceImplClass(BaseServiceImpl.class)
                .formatServiceFileName("I%sService")
                .formatServiceImplFileName("%sServiceImpl")
                .entityBuilder()
                .versionColumnName("version")
                //.versionPropertyName("version")
                .logicDeleteColumnName("deleted")
                .addTableFills(new Column("create_time", FieldFill.INSERT))
                .addTableFills(new Property("updateTime", FieldFill.INSERT_UPDATE))
                .formatFileName("%sPO")
                .enableLombok();

        /**
         * 包配置
         */
        String mapperPath = String.format("%s/%s/%s", dir, dto.getServiceName(), "src/main/resources/dao");
        Map<OutputFile, String> pathInfo = Collections.singletonMap(OutputFile.xml, mapperPath);
        Consumer<PackageConfig.Builder> packageConfig = builder -> builder
                .parent(dto.getPackageName())
                .entity("po")
                .mapper("dao")
                .xml("dao")
                .service("service")
                .serviceImpl("service.impl")
                .controller("controller")
                .pathInfo(pathInfo)
                .build();

        /**
         * 模板配置
         */
        Consumer<TemplateConfig.Builder> templateConfig = builder -> builder
                .service("/tplgen/service.java")
                .serviceImpl("/tplgen/serviceImpl.java")
                .controller("/tplgen/controller.java")
                .entity("/tplgen/po.java")
                .mapper("/tplgen/dao.java")
                .xml("/tplgen/xml.java")
                .build();

        /**
         * 注入配置
         */
        WebEnum webEnum = Arrays.stream(WebEnum.values()).filter(item -> item.getType().equals(dto.getWebType())).findFirst().get();
        Consumer<InjectionConfig.Builder> injectionConfig = builder -> builder
                .beforeOutputFile((tableInfo, objectMap) -> {
                    String MyModelName = tableInfo.getEntityName().substring(0, tableInfo.getEntityName().length() - 2);
                    String myModelName = MyModelName.substring(0, 1).toLowerCase() + MyModelName.substring(1);
                    objectMap.put("myModelName", MyModelName);
                    objectMap.put("lowModelName", myModelName);
                    objectMap.put("DtoName", MyModelName + "DTO");
                    objectMap.put("dtoName", myModelName + "DTO");

                    //配置页码生成
                    objectMap.put("WebName", MyModelName);
                    objectMap.put("webName", myModelName);
                })
                //.customMap(Collections.singletonMap("test", "baomidou"))
                .customFile(new HashMap<String, String>() {{
                    put("web", "/tplgen/" + webEnum.getFile());
                    put("dto", "/tplgen/dto.java.vm");
                }})
                .build();

        /**
         * 模板引擎配置
         */
        AbstractTemplateEngine templateEngine = new VelocityTemplateEngine() {
            @Override
            protected void outputCustomFile(List<CustomFile> customFiles, TableInfo tableInfo, Map<String, Object> objectMap) {
                String entityName = tableInfo.getEntityName().substring(0, tableInfo.getEntityName().length() - 2);
                customFiles.forEach(file -> {
                    String fileName = null;
                    if ("dto".equals(file.getFileName())) {
                        String pkgName = dto.getPackageName().replaceAll("\\.", Matcher.quoteReplacement(File.separator));
                        fileName = String.format((outputDir + File.separator + pkgName + File.separator + "dto" + File.separator + entityName + "DTO.java"));
                    }
                    if ("web".equals(file.getFileName())) {
                        fileName = String.format((dto.getWebPath() + File.separator + entityName + ".vue"));
                    }
                    outputFile(new File(fileName), objectMap, file.getTemplatePath(), dto.getFileOverride() == 1);
                });
            }
        };

        FastAutoGenerator.create(dto.getUrl(), dto.getUsername(), dto.getPassword())
                .globalConfig(globalConfig)//全局配置
                .strategyConfig(strategyConfig)//策略配置
                .packageConfig(packageConfig)//包配置
                .templateConfig(templateConfig)//模板配置
                .injectionConfig(injectionConfig)//注入配置
                .templateEngine(templateEngine)//模板引擎配置
                .execute();
    }
}